/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.gebze;

import com.asprise.util.pdf.PDFImageWriter;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import org.gebze.param.Parametre;
import org.gebze.param.TypeOfMessage;
import org.gebze.Util.GbzScannerClientHandler;

/**
 * Programın Server Tarafı
 *
 * @author Özcan ÇAĞIRICI <ozcan.cagirici@gmail.com>
 * @author Hatice Şahin <haticesahin.09@gmail.com>
 */
public class GbzServer
        extends GbzBase {

    private ServerSocket socket;
    private Map<String, GbzScannerClientHandler> sockets;

    /**
     * Default Constructor. Constructor çağrıldığında server ayarlarını yapar.
     * Dışarıdan tarama emri geldiğinde ilgili clienttan verileri alır.
     */
    public GbzServer()
            throws IOException {
        serverAyarlariniYap();
        socket = new ServerSocket(port_number);
        sockets = new HashMap<String, GbzScannerClientHandler>();
    

    }

    /**
     * verilen ip ile ilgili clienta erişir, geriye okunan datanın byte dizisini
     * gönderir.
     *
     * @param client
     * @return
     */
    public byte[] getImageFromClient(String client) {
        byte[] originalData = null;
        if (client != null) {
            GbzScannerClientHandler clientHandler = getClientHandler(client);
            InputStream is = clientHandler.getIn();
            int gelecekDosyaBoyutu = 0, tekrarSayisi = 0, artanMiktar = 0;

            try {
                gelecekDosyaBoyutu = is.read();
            } catch (IOException ex) {
                System.out.println("Clienttan veriler okunamadi. ");
                return originalData;
            }

            tekrarSayisi = gelecekDosyaBoyutu / Parametre.MAX_DATA_PARCA_BOYUTU;
            artanMiktar = gelecekDosyaBoyutu % Parametre.MAX_DATA_PARCA_BOYUTU;
            originalData = new byte[gelecekDosyaBoyutu];
            for (int i = 0; i < tekrarSayisi; i++) {
                originalData = parcaOkuBirlestir(is, originalData, Parametre.MAX_PARCA_BOYUTU);
            }
            if (artanMiktar != 0) {
                originalData = parcaOkuBirlestir(is, originalData, artanMiktar + Parametre.PARCA_HEADER_SIZE);
            }
        }

        return originalData;
    }

    /**
     * ServerSockete ait baglantiyi kurdurur. Eger baglanti kurulmussa,
     * baglantiya ait soketten IOStream tanımlayarak, işlemi ClientHandlera
     * aktarır.
     *
     * @param socket
     */
    @Override
    public boolean start() {
        if (socket != null) {
            boolean isServerStart = true;
            while (isServerStart) {
                Socket s = baglantiKur(socket);
                if (s != null) {    //eger hata olusursa yada kuyruk doluysa, socket null donecektir.
                    InputStream inStream = null;
                    OutputStream outStream = null;
                    try {
                        inStream = s.getInputStream();
                        outStream = s.getOutputStream();
                    } catch (IOException ex) {
                        System.out.println("Streamler acilamadi");
                    }
                    GbzScannerClientHandler clientHandler = new GbzScannerClientHandler(inStream, outStream);
                    sockets.put(s.getInetAddress().toString(), clientHandler);
//                    clientHandler.start();
//                    shareWithClient();
                }
            }
            return true;
        } else {
            System.out.println("Serversocket bulunmadıgı için işlem yapılamadı.");
            return false;
        }
    }

    /**
     * server clasındaki hashmapte bulunan ipleri listeler ve bir tanesini girdi
     * olarak okuyarak geri döndürür
     *
     * @return
     */
    private String getIP() {

        System.out.println("Bagli clientlarin IP bilgisi: ");
        Iterator iterator = sockets.keySet().iterator(); // iterator butun key yani ipleri listeler.
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }

        System.out.println("Mesaj gonderilecek IP seciniz: ");
        java.util.Scanner input = new java.util.Scanner(System.in);
        String hedefIP = input.next();

        //girilen deger, keylerden biri ile eslesiyor mu diye kontrol eder
        boolean eslesme = false;
        iterator = sockets.keySet().iterator();
        while (iterator.hasNext()) {
            if (iterator.next().toString().equals(hedefIP)) {
                eslesme = true;
            }
        }
        if (!eslesme) {
            System.out.println("Girdiginiz IP bilgisi yanlis..");
            getIP(); //girilen ifadeyi tekrar okuması için yeniden çağırdık...
        }
        return hedefIP;
    }

    /**
     * Verilen serverSocketten bir soket acarak baglanti bekler. Gelen
     * baglantiyi Map'e ekler ve acilan soketi dondurur.
     *
     * @param serSocket
     * @return
     */
    private Socket baglantiKur(ServerSocket serSocket) {
        Socket s = null;
        if (serSocket != null) {
            if (sockets.size() < max_connection_size) {
                try {
                    s = serSocket.accept();
                } catch (IOException ex) {
                    System.out.println("Baglanti kurulamadi");
                    s = null;
                }
            } else {
                System.out.println("Serverin tasiyabilecegi Client sayisi dolu!");
            }
        }
        return s;

    }

    /**
     * clienttan mesaj okur ve mesaj gonderir
     */
    private void shareWithClient() {
        boolean isEnd = false; //clienttan cikis komutu geldiğinde true olur
        String hedefIP;
        GbzScannerClientHandler hedefClient;
        TypeOfMessage serverMesaj;

        while (!isEnd) {
            hedefIP = getIP();
            hedefClient = getClientHandler(hedefIP);
            serverMesaj = getServerMessage();
            switch (serverMesaj) {
                case SCAN:

                    hedefClient.sendMessageToClient(hedefClient, "P");
                    byte[] kaynakByte = getImageFromClient(hedefIP);
                    if (kaynakByte != null) {
                        String mesaj = (kaynakByte.length + " uzunlugunda dosya alindi");
                        hedefClient.sendMessageToClient(hedefClient, mesaj);
                        saveByteArrayToPDF(kaynakByte);
                    } else {
                        String mesaj = ("dosya alinamadi");
                        hedefClient.sendMessageToClient(hedefClient, mesaj);
                    }
                    break;
                case MESSAGE:
                    hedefClient.sendMessageToClient(hedefClient, "M");
                    String clientMesaj = hedefClient.getClientMessage();
                    if (clientMesaj.equals(TypeOfMessage.EXIT.getCode())) {
                        isEnd = true;
                        sockets.remove(hedefIP);
                    } else {
                        System.out.println(clientMesaj);
                    }
                    break;
            }
        }
    }

    /**
     * verilen keye karsılık mapten value dondurur.
     *
     * @param ip
     * @return
     */
    private GbzScannerClientHandler getClientHandler(String ip) {
        return sockets.get(ip);
    }

    /**
     * serverın yapmak istediği işi, klavyeden girilen girdi ile belirler. Enum
     * olarak düzenlenmesi gerek
     *
     * @return
     * @throws IOException
     */
    private TypeOfMessage getServerMessage() {
        System.out.println("Clienttan mesaj okumak icin: " + TypeOfMessage.MESSAGE.getCode() + " , Tarama yapmak icin" + TypeOfMessage.SCAN.getCode() + " giriniz");
        java.util.Scanner input = new java.util.Scanner(System.in);
        String mesaj = input.next();
        TypeOfMessage retValue = null;

        if (TypeOfMessage.SCAN.getCode().equals(mesaj)) {
            retValue = TypeOfMessage.SCAN;
        } else if (TypeOfMessage.MESSAGE.getCode().equals(mesaj)) {
            retValue = TypeOfMessage.MESSAGE;
        } else {
            System.out.println("Yanlis karakter girdiniz, tekrar deneyin");
            getServerMessage();
        }
        return retValue;

    }

    /**
     * servera ait ayarlar yapılır.
     */
    private void serverAyarlariniYap() {
        Properties prop = new Properties();
        String fileName = Parametre.SERVER_CONFIG_FILE_NAME;
        try {
            InputStream is = new FileInputStream(fileName);
            prop.load(is);
        } catch (IOException e) {
            System.out.println("Dosya yuklerken hata olustu. Varsayılan ayarlarla devam ediliyor: " + e);

        }
        try {
            port_number = Integer.parseInt(prop.getProperty("port_no"));
            max_connection_size = Integer.parseInt(prop.getProperty("kuyruk_boyutu"));
        } catch (NumberFormatException e) {
            System.out.println("Veriler okunamadi. Varsaılan ayarlarla devam ediliyor: " + e.getMessage());

        }

    }

    /**
     * parametre olarak aldıgı diziyi belirtilen yola pdf formmatında kaydeder.
     *
     * @param kaynakByte
     */
    private void saveByteArrayToPDF(byte[] kaynakByte) {
        if (kaynakByte == null) {
            System.out.println("Kaynak boş oldugu için pdf hazırlanamdı");
        } else {
            PDFImageWriter writer = null;
            BufferedImage resim = null;
            try {
                writer = new PDFImageWriter(new FileOutputStream(Parametre.SAVE_PDF_PATH));
            } catch (FileNotFoundException ex) {
                System.out.println("PDF için yol bulunamadi.. :" + ex);
            }
            writer.open();
            writer.addImage(resim);
            writer.close();
        }

    }

    /**
     * Verilen byte dizisine ait header bilgisini okuyarak, headerı geri
     * dondurur.
     *
     * @param b
     * @param offset
     * @return
     */
    private int Convert32BitsToInt(byte[] b, int offset) {
        int value = 0;
        for (int i = 0; i < 4; i++) {
            int k = (4 - 1 - i) * 8;
            value += (b[i + offset] & 0x000000FF) << k;
        }
        return value;
    }

    /**
     * gelen byte dizisine ait header bilgisini cikararak orjinal veri dizisini
     * dondurur.
     *
     * @param okunanParca
     * @return
     */
    private byte[] getOriginalParca(byte[] okunanParca) {
        byte[] retValue = null;

        retValue = new byte[okunanParca.length - Parametre.PARCA_HEADER_SIZE];
        int baslangic_degeri = 0;
        for (int i = Parametre.PARCA_HEADER_SIZE; i < okunanParca.length; i++) {
            retValue[baslangic_degeri++] = okunanParca[i];
        }
        return retValue;
    }

    /**
     *
     * Kaynak dizinin elemanlarını, verilen parca sirasina gore hedef arraye
     * atar.
     *
     * @param hedefArray
     * @param kaynakArray
     * @param parcaSirasi
     * @return
     */
    private byte[] setParcaBirlestir(byte[] hedefArray, byte[] kaynakArray, int parcaSirasi) {
        int index = parcaSirasi * Parametre.MAX_DATA_PARCA_BOYUTU;
        for (int j = 0; j < kaynakArray.length; ++j) {
            hedefArray[index++] = kaynakArray[j];
        }
        return hedefArray;
    }

    /**
     * Inputstreamden lenght kadar veriyi okur, header bilgisni çıkarıp olusan
     * veriyi, parametre olarak gelen diziye ekler
     *
     * @param is
     * @param originalData
     * @param length
     * @return
     */
    private byte[] parcaOkuBirlestir(InputStream is, byte[] originalData, int length) {
        byte[] okunanParca = new byte[length];
        byte[] originalParca = new byte[length - Parametre.PARCA_HEADER_SIZE];
        try {
            is.read(okunanParca);
        } catch (IOException ex) {
            System.out.println("Parca okunamadi.");
        }
        int headerNo = Convert32BitsToInt(okunanParca, 0);
        originalParca = getOriginalParca(okunanParca);
        originalData = setParcaBirlestir(originalData, originalParca, headerNo);
        return originalData;

    }
}
